package com.woniu.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.woniu.constants.CommonConstants;
import com.woniu.constants.ErrorConstants;
import com.woniu.constants.RedisConstants;
import com.woniu.constants.WeChatConstants;
import com.woniu.entity.*;
//import com.woniu.entity.common.TextMessage;
import com.woniu.entity.idiom.IdiomInviteRecEntity;
import com.woniu.entity.idiom.IdiomRedBeansRecEntity;
import com.woniu.exception.MyErrorException;
import com.woniu.mapper.NuserSubscriptionMapper;
import com.woniu.mapper.WechatTemplateMessageMapper;
import com.woniu.service.*;
import com.woniu.service.idiom.IdiomInviteRecService;
import com.woniu.service.idiom.IdiomRedBeansRecService;
import com.woniu.utils.*;
import com.woniu.vo.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
//import org.dom4j.Document;
//import org.dom4j.DocumentException;
//import org.dom4j.Element;
//import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.concurrent.TimeUnit;


@Service
public class WechatServiceImpl implements WechatService {

    private static final Logger logger = LoggerFactory.getLogger(WechatServiceImpl.class);


    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RestTemplateUtil restTemplateUtil;

    @Autowired
    private NuserService nuserService;

//    @Autowired
//    private NuserShareService nuserShareService;

    @Autowired
    private WechatExceptionsService wechatExceptionsService;

    @Autowired
    private WechatPayLogService wechatPayLogService;

    @Autowired
    private WechatTemplateMessageMapper wechatTemplateMessageMapper;

    @Autowired
    private NuserSubscriptionMapper nuserSubscriptionMapper;

    @Autowired
    private TokenUtil tokenUtil;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private IdiomInviteRecService idiomInviteRecService;

    @Autowired
    private IdiomRedBeansRecService idiomRedBeansRecService;

    @Value("${wechatPay.mchid}")
    private String payMchid;

    @Value("${wechatPay.serialNo}")
    private String paySerialNo;

    @Value("${wechatPay.apiclientCertPath}")
    private String payApiclientCertPath;


    /**
     * 微信登录小程序授权登录
     *
     * @param code code
     * @return
     */
    @Override
    public TokenVO weChatAuthorizeProgramLogin(JSONObject code) {
        //System.out.println("wechat登录参数:"+code.toJSONString());
        WeChatMiniAuthorizeVo response = this.miniAuthCode(code);
//        WeChatMiniAuthorizeVo response = new WeChatMiniAuthorizeVo();
//        response.setOpenId("ospFe43DQKDHoUjfDFdlOGZD-hmI");
        System.out.println("小程序登陆成功 = " + JSON.toJSONString(response));
        //检测是否存在
        Nuser nuser = nuserService.getOne(new LambdaQueryWrapper<Nuser>()
                .eq(Nuser::getOpenid, response.getOpenId()));

        boolean isSave = false;
        if (ObjectUtil.isEmpty(nuser)) {
            isSave = true;

            String uid = UUID.randomUUID().toString();
            nuser = Nuser.builder()
                    .openid(response.getOpenId())
                    .unionid(response.getUnionId())
                    .id(uid)
                    .build();
            // 如果推荐人ID有传过来，则绑定
            String inviteUid = code.getString("shareId");
            if (!StringUtils.isEmpty(inviteUid)) {
                Nuser inviteUser = nuserService.getById(inviteUid);
                if (inviteUser != null) {
                    nuser.setInviteUid(inviteUser.getId());

                    // 成语查看记录数，小于等于5加红豆
                    long count = idiomInviteRecService.count(new QueryWrapper<IdiomInviteRecEntity>()
                            .eq("from_uid", inviteUid));
                    if (count <= 5) {
                        IdiomRedBeansRecEntity entity = new IdiomRedBeansRecEntity();
                        entity.setBeansCount(30);
                        entity.setUid(inviteUid);
                        entity.setType("邀请好友学成语");
                        idiomRedBeansRecService.save(entity);
                        // 这里不加缓存邀请数量，加已触发邀请，放到主页去加
                        redisUtil.setPermanentString("idiom:share:" + inviteUid, "1");
                    }
                    //这里记录成语邀请的信息
                    IdiomInviteRecEntity entity = new IdiomInviteRecEntity();
                    entity.setFromUid(inviteUid);
                    entity.setInviteId(uid);
                    idiomInviteRecService.save(entity);

                }
            }
        }


        //OcUser user = ocUserList.get(0);
        return this.login(nuser, isSave);
    }


    /**
     * 小程序登录凭证校验
     *
     * @return 小程序登录校验对象
     */
    @Override
    public WeChatMiniAuthorizeVo miniAuthCode(JSONObject code) {
        String appId = WeChatConstants.WECHAT_MINI_APPID;
        if (StrUtil.isBlank(appId)) {
            throw new RuntimeException("微信小程序appId未配置");
        }
        String secret = WeChatConstants.WECHAT_MINI_APPSECRET;
        if (StrUtil.isBlank(secret)) {
            throw new RuntimeException("微信小程序secret未配置");
        }
        String url = StrUtil.format(WeChatConstants.WECHAT_MINI_SNS_AUTH_CODE2SESSION_URL, appId, secret, code.getString("code"));
        JSONObject data = restTemplateUtil.getData(url);
        System.out.println("========data:" + data);
        if (ObjectUtil.isNull(data)) {
            throw new RuntimeException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, "微信小程序登录凭证校验异常");
//                throw new RuntimeException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                throw new RuntimeException("微信接口调用失败");
            }
        }
        return JSONObject.parseObject(data.toJSONString(), WeChatMiniAuthorizeVo.class);
    }


    /**
     * 获取小程序accessToken
     *
     * @return accessToken
     */
    @Override
    public String getMiniAccessToken() {
        boolean exists = redisUtil.exists(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
        if (exists) {
            Object accessToken = redisUtil.get(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
            return accessToken.toString();
        }
        String appId = WeChatConstants.WECHAT_MINI_APPID;
        if (StrUtil.isBlank(appId)) {
            throw new RuntimeException("微信小程序appId未设置");
        }
        String secret = WeChatConstants.WECHAT_MINI_APPSECRET;
        if (StrUtil.isBlank(secret)) {
            throw new RuntimeException("微信小程序secret未设置");
        }
        WeChatAccessTokenVo accessTokenVo = this.getAccessToken(appId, secret, "mini");
        // 缓存accessToken
        redisUtil.set(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY, accessTokenVo.getAccessToken(),
                accessTokenVo.getExpiresIn().longValue() - 1800L, TimeUnit.SECONDS);
        return accessTokenVo.getAccessToken();
    }


    /**
     * 获取公众号accessToken
     */
    @Override
    public String getPublicAccessToken() {
        boolean exists = redisUtil.exists(WeChatConstants.REDIS_WECAHT_PUBLIC_ACCESS_TOKEN_KEY);
        if (exists) {
            Object accessToken = redisUtil.get(WeChatConstants.REDIS_WECAHT_PUBLIC_ACCESS_TOKEN_KEY);
            return accessToken.toString();
        }
//        String appId = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPID);
//        if (StrUtil.isBlank(appId)) {
//            throw new MyErrorException("微信公众号appId未设置");
//        }
        String appId = WeChatConstants.WECHAT_PUBLIC_APPID;
        String secret = WeChatConstants.WECHAT_PUBLIC_APPSECRET;
//        String secret = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPSECRET);
//        if (StrUtil.isBlank(secret)) {
//            throw new MyErrorException("微信公众号secret未设置");
//        }
        WeChatAccessTokenVo accessTokenVo = getAccessToken(appId, secret, "public");
        // 缓存accessToken
        redisUtil.set(WeChatConstants.REDIS_WECAHT_PUBLIC_ACCESS_TOKEN_KEY, accessTokenVo.getAccessToken(),
                accessTokenVo.getExpiresIn().longValue() - 1800L, TimeUnit.SECONDS);
        return accessTokenVo.getAccessToken();
    }

    /**
     * 记录订阅
     *
     * @param templateId 模板表ID
     * @return
     */
    @Override
    public boolean subscription(Integer templateId) {
        String userId = tokenUtil.getUserId();
        // 判断模板ID是否存在
        if (!wechatTemplateMessageMapper.exists(new LambdaQueryWrapper<WechatTemplateMessage>()
                .eq(WechatTemplateMessage::getId, templateId))) {
            throw new MyErrorException(ErrorConstants.WECHAT_TEMPLATE_IS_NUL);
        }

        return addSubscription(templateId, userId);
    }

    /**
     * 添加订阅
     *
     * @param templateId 模板ID
     * @param userId     用户ID
     * @return
     */
    private boolean addSubscription(Integer templateId, String userId) {
        // 判断用户是否已经订阅过
        if (nuserSubscriptionMapper.exists(new LambdaQueryWrapper<NuserSubscription>()
                .eq(NuserSubscription::getNuserId, userId)
                .eq(NuserSubscription::getTemplateId, templateId))) {
            return true;
        }


        return nuserSubscriptionMapper.insert(NuserSubscription.builder().nuserId(userId).templateId(templateId).build()) > 0;
    }

    /**
     * 小程序获取用户手机号
     *
     * @return 小程序获取用户手机号
     */
    private JSONObject getuserphonenumber(JSONObject json) {
        String url = StrUtil.format(WeChatConstants.WECHAT_MINI_GET_USER_PHONE_NUMBER_URL, getMiniAccessToken());
        JSONObject data = restTemplateUtil.postJsonDataAndReturnJson(url, new JSONObject() {{
            put("code", json.getString("code"));
        }});
        if (ObjectUtil.isNull(data)) {
            throw new RuntimeException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, "微信小程序获取用户手机号异常");
//                throw new RuntimeException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                throw new RuntimeException("微信接口调用失败");
            }
        }
        return data;
    }

    /**
     * 获取微信accessToken
     *
     * @param appId  appId
     * @param secret secret
     * @param type   mini-小程序，public-公众号，app-app
     * @return WeChatAccessTokenVo
     */
    private WeChatAccessTokenVo getAccessToken(String appId, String secret, String type) {
        String url = StrUtil.format(WeChatConstants.WECHAT_ACCESS_TOKEN_URL, appId, secret);
        JSONObject data = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(data)) {
            throw new RuntimeException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, StrUtil.format("微信获取accessToken异常，{}端", type));
//                throw new RuntimeException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                throw new RuntimeException("微信接口调用失败");
            }
        }
        return JSONObject.parseObject(data.toJSONString(), WeChatAccessTokenVo.class);
    }

    /**
     * 微信异常处理
     *
     * @param jsonObject 微信返回数据
     * @param remark     备注
     */
    private void wxExceptionDispose(JSONObject jsonObject, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        wechatExceptions.setErrcode(jsonObject.getString("errcode"));
        wechatExceptions.setErrmsg(StrUtil.isNotBlank(jsonObject.getString("errmsg")) ? jsonObject.getString("errmsg") : "");
        wechatExceptions.setData(jsonObject.toJSONString());
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }


    /**
     * 微信支付日志处理
     *
     * @param jsonObject 微信返回数据
     * @param openid     用户openid
     */
    private void wxPayLogDispose(JSONObject jsonObject, String openid) {

        WechatPayLog wechatPayLog = new WechatPayLog();
        wechatPayLog.setErrcode(jsonObject.getString("code"));
        wechatPayLog.setData(jsonObject.toJSONString());
        wechatPayLog.setOpenid(openid);
        wechatPayLog.setCreateTime(DateUtil.date());

        wechatPayLogService.save(wechatPayLog);
    }


    private TokenVO login(Nuser user, boolean isSave) {

        if (isSave) {

            // ID为空则为新增
            if (StringUtils.isEmpty(user.getId())) {
                // 设置默认昵称
                user.setNickname("小龙人" + RandomUtil.randomNumbers(8));
                // 设置默认头像
                user.setAvatar(CommonConstants.AVATAR_URL);

//                Boolean saveFlag = transactionTemplate.execute(e -> {
//                    nuserService.save(user);
//                    nuserShareService.save(NuserShare.builder().nuserId(user.getId()).build());
//
//                    return Boolean.TRUE;
//
//                });

                // 失败了则报错
                if (!nuserService.save(user)) {
                    throw new RuntimeException("账号注册失败~");
                }

            }
            // 更新
            else {
                //保存关联的信息
                nuserService.updateById(user);
            }

        }

//        NuserShare nuserShare = nuserShareService.getOne(new LambdaQueryWrapper<NuserShare>()
//                .eq(NuserShare::getNuserId, user.getId()));

        TokenVO tokenVO = new TokenVO();
        UserVO userVO = ConverterUtil.convert(user, UserVO.class);
        userVO.setUserId(user.getId());
        //userVO.setShareId(nuserShare.getId());
        tokenVO.setUser(userVO);
        try {
            tokenUtil.createToken(tokenVO);
        } catch (Exception e) {
            logger.error(StrUtil.format("小程序登录生成token失败，uid={}", user.getId()));
            e.printStackTrace();
        }

        return tokenVO;
    }

    /**
     * 微信小程序发送订阅消息
     *
     * @param templateMessage 消息对象
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMiniSubscribeMessage(ProgramTemplateMessageVo templateMessage) {
        String accessToken = getMiniAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_MINI_SEND_SUBSCRIBE_URL, accessToken);
        JSONObject messAge = JSONObject.parseObject(JSONObject.toJSONString(templateMessage));
        String result = restTemplateUtil.postJsonData(url, messAge);
        JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            //throw new MyErrorException("微信平台接口异常，没任何数据返回！");
            logger.info("微信平台接口异常，没任何数据返回！");
            return Boolean.FALSE;
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.getString("errcode").equals("40001")) {
                wxExceptionDispose(data, "微信小程序发送订阅消息异常");
                redisUtil.delete(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
                accessToken = getMiniAccessToken();
                url = StrUtil.format(WeChatConstants.WECHAT_MINI_SEND_SUBSCRIBE_URL, accessToken);
                result = restTemplateUtil.postJsonData(url, messAge);
                JSONObject data2 = JSONObject.parseObject(result);
                if (data2.containsKey("errcode") && !data2.getString("errcode").equals("0")) {
                    if (data2.containsKey("errmsg")) {
                        wxExceptionDispose(data2, "微信小程序发送订阅消息重试异常");
                        //throw new MyErrorException("微信接口调用失败：" + data2.getString("errcode") + data2.getString("errmsg"));
                        logger.info("微信接口调用失败：" + data2.getString("errcode") + data2.getString("errmsg"));
                        return Boolean.FALSE;
                    }
                } else {
                    return Boolean.TRUE;
                }
            }
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信小程序发送订阅消息异常");
                //throw new MyErrorException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                logger.info("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 微信公众号发送模板消息
     *
     * @param templateMessage 模板消息对象
     * @return 是否发送成功
     */
    @Override
    public Boolean sendPublicTemplateMessage(ProgramTemplateMessageVo templateMessage) {
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_SEND_TEMPLATE_URL, accessToken);
        JSONObject jsonData = JSONObject.parseObject(JSONObject.toJSONString(templateMessage));
        String result = restTemplateUtil.postJsonData(url, jsonData);
        JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            throw new MyErrorException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信公众号发送模板消息异常");
                throw new MyErrorException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 组装数据和发送订阅消息
     *
     * @param templateId 模板ID
     * @param temMap     发送内容
     * @param openId     用户openId
     */
    @Override
    public void pushMiniTemplateMessage(String templateId, HashMap<String, String> temMap, String openId) {
        ProgramTemplateMessageVo programTemplateMessageVo = new ProgramTemplateMessageVo();
        programTemplateMessageVo.setTemplate_id(templateId);

        //组装关键字数据
        HashMap<String, SendProgramTemplateMessageItemVo> hashMap = new HashMap<>();
        temMap.forEach((key, value) -> hashMap.put(key, new SendProgramTemplateMessageItemVo(value)));

        programTemplateMessageVo.setData(hashMap);
        programTemplateMessageVo.setTouser(openId);

        // 发送订阅消息
        redisUtil.lPush(WeChatConstants.WE_CHAT_MESSAGE_KEY_PROGRAM, JSONObject.toJSONString(programTemplateMessageVo));
    }

    /**
     * 组装数据和发送公众号消息
     *
     * @param openid 用户openid
     */
    @Override
    public void pushPublicCustomMessage(String openid) {
        PublicCustomProgramMessageVo publicCustomProgramMessageVo = new PublicCustomProgramMessageVo();
        publicCustomProgramMessageVo.setTouser(openid);

        PublicCustomProgramMessageVo.MiniprogrampageData miniprogrampageData = new PublicCustomProgramMessageVo.MiniprogrampageData();
        miniprogrampageData.setTitle("发红包了，快来抢啊");
        miniprogrampageData.setAppid(WeChatConstants.WECHAT_MINI_APPID);
        miniprogrampageData.setPagepath("pages/index/index");
        miniprogrampageData.setThumb_media_id(WeChatConstants.WECHAT_PUBLIC_PROGRAM_MEDIA_ID);
        publicCustomProgramMessageVo.setMiniprogrampage(miniprogrampageData);

        // 记录到redis,等待发送
        redisUtil.lPush(WeChatConstants.WE_CHAT_CUSTOM_KEY_PUBLIC, JSONObject.toJSONString(publicCustomProgramMessageVo));

    }

    /**
     * 转账到用户零钱
     *
     * @param outBatchNo 商家批次号
     * @param money      金额
     * @param openid     用户openid
     * @return
     */
    @Override
    public boolean transferAccounts(String outBatchNo, Integer money, String openid) {
        // 商户号
        String mchid = this.payMchid;
        // 商户证书路径（在你本机测试时放你本机路径中的就可以）
        Map<String, Object> postMap = new HashMap<>();
        // 申请商户号的appid或商户号绑定的appid（企业号corpid即为此appid）
        postMap.put("appid", WeChatConstants.WECHAT_MINI_APPID);
        // 商家批次单号 长度 1~32
        postMap.put("out_batch_no", outBatchNo);
        // 该笔批量转账的名称
        postMap.put("batch_name", "新年味活动");
        // 转账说明，UTF8编码，最多允许32个字符
        postMap.put("batch_remark", "新年味红包活动");
        // 总金额 单位为“分”
        postMap.put("total_amount", money);
        // 转账总笔数
        postMap.put("total_num", 1);
        // 转账明细列表
        List<Map<String, Object>> list = new ArrayList<>();
        // 转账明细
        Map<String, Object> subMap = new HashMap<>(4);
        // 商家批次单号
        String outDetailNo = IdUtil.getSnowflake(0, 0).nextIdStr();
        subMap.put("out_detail_no", outDetailNo);
        // 转账金额
        subMap.put("transfer_amount", money);
        // 转账备注
        subMap.put("transfer_remark", "热热闹闹过个年，一起放烟花，抢红包，过大年～");
        // 用户在直连商户应用下的用户标示
        subMap.put("openid", openid);
        list.add(subMap);
        postMap.put("transfer_detail_list", list);
        // 发起转账操作
        String payResult = postTransBatRequest(JSON.toJSONString(postMap), mchid);
        // 返回结果是空则返回
        if (StringUtils.isEmpty(payResult)) {
            return false;
        }
        JSONObject data = JSONObject.parseObject(payResult);

        // 先记录日志
        wxPayLogDispose(data, openid);

        boolean flag = false;
        String code = data.getString("code");
        String batchStatus = data.getString("batch_status");
        // code是空且batchStatus不为空且等于ACCEPTED
        if (StringUtils.isEmpty(code) && !StringUtils.isEmpty(batchStatus) && batchStatus.equals("ACCEPTED")) {
            flag = true;
        }

        return flag;
    }

    /**
     * 公众号发送小程序卡片消息
     *
     * @param publicCustomProgramMessageVo 消息对象
     * @return
     */
    @Override
    public Boolean sendPulicCustomProgramMessage(PublicCustomProgramMessageVo publicCustomProgramMessageVo) {
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_SEND_CUSTOM_URL, accessToken);
        JSONObject jsonData = JSONObject.parseObject(JSONObject.toJSONString(publicCustomProgramMessageVo));
        String result = restTemplateUtil.postJsonData(url, jsonData);
        JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            //throw new MyErrorException("微信平台接口异常，没任何数据返回！");
            logger.info("微信平台接口异常，没任何数据返回！");
            return Boolean.FALSE;
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信公众号发送模板消息异常");
                //throw new MyErrorException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                logger.info("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    @Override
    public Boolean sendPulicCustomTextMessage(PublicCustomTextMessageVo publicCustomTextMessageVo) {
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_SEND_CUSTOM_URL, accessToken);
        JSONObject jsonData = JSONObject.parseObject(JSONObject.toJSONString(publicCustomTextMessageVo));
        String result = restTemplateUtil.postJsonData(url, jsonData);
        JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            //throw new MyErrorException("微信平台接口异常，没任何数据返回！");
            logger.info("微信平台接口异常，没任何数据返回！");
            return Boolean.FALSE;
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信公众号发送模板消息异常");
                //throw new MyErrorException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                logger.info("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 公众号获取用户openid列表
     *
     * @param nextOpenid 下一个openid，也就是从哪个openid开始查起
     * @return
     */
    @Override
    public WechatPublicUserListVO getPublicUserList(String nextOpenid) {
        WechatPublicUserListVO result = new WechatPublicUserListVO();

        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_USER_GET_URL, accessToken, StringUtils.isEmpty(nextOpenid) ? "" : nextOpenid);
        //JSONObject jsonData = JSONObject.parseObject(JSONObject.toJSONString(publicCustomMessageVo));
        JSONObject data = restTemplateUtil.getData(url);
        //JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            return result;
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信公众号获取用户列表异常");
                return result;
            }
        }

        JSONObject dataJSONObject = data.getJSONObject("data");

        if (dataJSONObject == null || dataJSONObject.size() == 0) {
            return result;
        }

        // 用户列表
        JSONArray openidArray = dataJSONObject.getJSONArray("openid");
        if (openidArray == null || openidArray.size() == 0) {
            return result;
        }

        List<String> openids = openidArray.toJavaList(String.class);
        result.setUserOpenids(openids);


        // 下一个用户ID
        String nextOpenidResult = data.getString("next_openid");
        result.setNextOpenid(nextOpenidResult);


        return result;
    }

    /**
     * 公众号获取用户详情
     *
     * @param openid 用户openid
     * @return
     */
    @Override
    public WechatPublicUserVO getPublicUser(String openid) {
        WechatPublicUserVO result = new WechatPublicUserVO();

        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_USER_INFO_URL, accessToken, openid);
        //JSONObject jsonData = JSONObject.parseObject(JSONObject.toJSONString(publicCustomMessageVo));
        JSONObject data = restTemplateUtil.getData(url);
        //JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            throw new MyErrorException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信公众号获取用户详情异常");
                throw new MyErrorException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }

        // 获取用户是否订阅
        result.setSubscribe(data.getInteger("subscribe"));
        // 获取用户订阅时间
        result.setSubscribeTime(data.getLong("subscribe_time") * 1000);
        // 获取用户openid
        result.setOpenid(data.getString("openid"));
        // 获取用户unionid
        result.setUnionid(data.getString("unionid"));


        return result;


    }


    private static String creatSHA1(String token, String timestamp, String nonce) {
        try {
            String[] array = new String[]{token, timestamp, nonce};
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < 3; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new MyErrorException("AES加密出错");
        }
    }


    /**
     * 发起批量转账API 批量转账到零钱
     * 证书下载地址：需要指到文件 例如：D:\XXX.pem
     *
     * @param requestJson 组合参数
     * @param mchID4M     商户号
     * @return String
     */
    private String postTransBatRequest(String requestJson, String mchID4M) {
        try {
            CloseableHttpClient httpclient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/transfer/batches");
            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore，不能正确的设置字符集，可能导致签名错误
            httpPost.addHeader("Content-Type", "application/json");
            httpPost.addHeader("Accept", "application/json");
            // 文档证书序列号
            httpPost.addHeader("Wechatpay-Serial", this.paySerialNo);
            // -------------------------核心认证 start-----------------------------------------------------------------
            String strToken = getToken("POST",
                    "/v3/transfer/batches",
                    requestJson, mchID4M, this.paySerialNo, this.payApiclientCertPath);
            // 添加认证信息
            httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + strToken);
            // ---------------------------核心认证 end---------------------------------------------------------------
            httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));
            // 发起转账请求
            CloseableHttpResponse response = httpclient.execute(httpPost);
            // 获取返回的数据
            HttpEntity entity = response.getEntity();
            return EntityUtils.toString(entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param method       请求方法 post
     * @param canonicalUrl 请求地址
     * @param body         请求参数
     * @param merchantId   这里用的商户号
     * @param certSerialNo 商户证书序列号
     * @param keyPath      商户证书地址
     * @return String
     */
    private String getToken(String method, String canonicalUrl, String body, String merchantId,
                            String certSerialNo, String keyPath) throws Exception {
        // 获取32位随机字符串
        String nonceStr = getRandomString(32);
        // 当前系统运行时间
        long timestamp = System.currentTimeMillis() / 1000;
        if (org.apache.commons.lang3.StringUtils.isEmpty(body)) body = "";
        String message = method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
        // 签名操作
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message.getBytes(StandardCharsets.UTF_8));
        String signature = Base64.encodeBase64String(sign.sign());
        // 组装参数
        return "mchid=\"" + merchantId + "\",timestamp=\"" + timestamp + "\",nonce_str=\"" + nonceStr +
                "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\"";
    }

    /**
     * 微信支付-前端唤起支付参数-获取商户私钥
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    private PrivateKey getPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), StandardCharsets.UTF_8);
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    private String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }


}
